package registnet.cpm;

import java.util.List;
import java.util.Vector;

import registnet.cpm.util.EarlyStartEarlyFinishImp;
import registnet.cpm.util.IVisitor;
import registnet.cpm.util.LateStartLateFinishImp;
import registnet.cpm.util.Queue;

/**
 * An implementation of the Critical Path Method. This implementation uses a
 * Activity-on-Node network diagram.
 */
public class CPM {
	
	/**
	 * The project that the CPM will process.
	 */
	private Project project;
	
	/**
	 * Type of searching: <i>FORWARD</i>
	 */
	private static int FORWARD = 0;
	
	/**
	 * Type of searching: <i>BACKWARD</i>
	 */
	private static int BACKWARD = 1;
	
	/**
	 * Creates a <code>CMP</code> instance that will process the given project.
	 * @param project
	 */
	public CPM(Project project){
		this.project = project;
		getCriticalNodes();
	}
	
	/**
	 * Breadth-First Search implementation which allows revisiting of visited nodes.
	 * 
	 * @param visitor - <code>IVisitor</code> class implementation; A visitor class makes the 
	 * processing in the node which is visited.
	 * @param type - type of searching. Possible values are <code>FORWARD</code> or <code>BACKWARD</code>
	 */
	private void bfs(IVisitor visitor, int type) {
		Queue queue = new Queue();

		// Initialize the nodes
		for(int i=0; i<project.getNodes().size(); i++)
			project.getNodes().get(i).setVisited(false);
		
		// Add start node/s to the queue
		if(type == FORWARD) {
			for(int i=0; i<project.getStartNodes().size(); i++)
				queue.enque(project.getStartNodes().get(i));
		}else if (type == BACKWARD) {
			for(int i=0; i<project.getEndNodes().size(); i++)
				queue.enque(project.getEndNodes().get(i));
		}
		

		// While there are still items in queue
		while (!queue.isEmpty()) {
			//Remove a node from queue
			Node node = (Node) queue.dequeue();
			
			//Visit a node in the queue
			//if (!node.isVisited()){
				visitor.visit(project, node);
				//activity.getNodes().get(activity.getIndex(node)).setVisited(true);
			//}
				
			//Put in queue all nodes adjacent to node visited
			if(type == FORWARD) {
				for(int i=0; i<project.getSuccessors(project.getNodes().get(project.getIndex(node))).size(); i++){
					//if(!activity.getSuccessors(activity.getNodes().get(activity.getIndex(node))).get(i).isVisited())
						queue.enque(project.getSuccessors(project.getNodes().get(project.getIndex(node))).get(i));
				}
			}else if (type == BACKWARD) {
				for(int i=0; i<project.getPredecessors(project.getNodes().get(project.getIndex(node))).size(); i++){
					//if(!activity.getPredecessors(activity.getNodes().get(activity.getIndex(node))).get(i).isVisited())
						queue.enque(project.getPredecessors(project.getNodes().get(project.getIndex(node))).get(i));
				}
			}
		}
	}
	
	/**
	 * Determine the critical nodes. Critical nodes are nodes that lie in the critical path.
	 * @return list of the critical nodes.
	 */
	public List<Node> getCriticalNodes(){
		List<Node> retVal = new Vector<Node>();
		// forward pass
		bfs(new EarlyStartEarlyFinishImp(), FORWARD);
		project.computeActivityDuration();
		
		//backward pass
		bfs(new LateStartLateFinishImp(), BACKWARD);
		for(int i=0; i<project.getNodes().size(); i++){
			project.getNodes().get(i).setSlack(project.getNodes().get(i).getLateStart() - project.getNodes().get(i).getEarlyStart());
			if(project.getNodes().get(i).getSlack() == 0)
				retVal.add(project.getNodes().get(i));
		}
		return retVal;
	}
	
	/**
	 * Prints the basic information about the nodes in the project processed.
	 */
	public void printNodeData(){
		System.out.println("[NODE] <name>-<ES>-<EF>-<LS>-<LF>-<slack>");
		for(int i=0; i<project.getNodes().size(); i++){
			System.out.println("[NODE] " + project.getNodes().get(i).getName()+"-"+project.getNodes().get(i).getEarlyStart()+"-"+project.getNodes().get(i).getEarlyFinish()+"-"+project.getNodes().get(i).getLateStart()+"-"+project.getNodes().get(i).getLateFinish()+"-"+project.getNodes().get(i).getSlack());
		}
	}
	
	/**
	 * Prints the basic information about the arcs in the project processed.
	 */
	public void printArcData(){
		List<Arc> arcs = project.getArcs();
		System.out.println("[ARC] <name>");
		for(int i=0; i<arcs.size(); i++){
			System.out.println("[ARC] " + arcs.get(i).getName());
		}
	}

	/**
	 * Returns the project being processed.
	 * @return the project being processed
	 */
	public Project getProject() {
		return project;
	}
	
}